Here is the expanded and comprehensive architectural tree for the Jobcard Management System. It now includes approval workflows, costing, invoicing, financials, a dedicated client portal, role-based dashboards, reporting, and other essential enterprise features.

1. Expanded Directory Structure (File Tree)
text

jobcard-system/
│
├── config/                     # Configuration settings
│   ├── database.php            # PHP PDO connection to MySQL
│   ├── app_config.php          # System-wide settings (currency, tax rates)
│   └── permissions.php         # Role definitions (Admin, Tech, Accountant, Client)
│
├── includes/                   # Reusable components and helpers
│   ├── staff_header.php        # HTML <head>, Navbar for Staff
│   ├── staff_footer.php        # Footer HTML & JS scripts for Staff
│   ├── client_header.php       # HTML <head>, Navbar for Client Portal
│   ├── client_footer.php       # Footer HTML & JS scripts for Client Portal
│   ├── auth_check.php          # Checks if staff is logged in
│   ├── client_auth_check.php   # Checks if client is logged in
│   └── functions.php           # Helper functions (e.g., format_currency, generate_pdf)
│
├── controllers/                # Business logic (processes forms, talks to DB)
│   ├── AuthController.php      # Staff login/logout
│   ├── ClientAuthController.php # Client login/logout
│   ├── DashboardController.php # Aggregates data for dashboards
│   ├── JobcardController.php   # Create, Update, Delete jobcards
│   ├── ApprovalController.php  # Handle approvals/rejections (Jobcards & Invoices)
│   ├── CostingController.php   # Manage parts, labor, and additional costs
│   ├── InvoiceController.php   # Generate invoices from jobcards
│   ├── PaymentController.php   # Record payments, handle financials
│   ├── ReportController.php    # Generate financial and operational reports
│   ├── ClientPortalController.php # Handles client-facing actions
│   └── SettingsController.php  # Manage users, company info, templates
│
├── models/                     # Data representation & DB queries
│   ├── User.php                # Staff users (Admins, Techs, Accountants)
│   ├── Customer.php            # Clients (Includes portal login credentials)
│   ├── Jobcard.php             # Jobcards
│   ├── Approval.php            # Approval workflow steps
│   ├── CostItem.php            # Parts and Labor costs
│   ├── Invoice.php             # Invoices
│   ├── Payment.php             # Financial payments
│   └── Report.php              # Complex SQL queries for reporting
│
├── views/                      # HTML/UI files presented to the user
│   ├── staff/                  # --- STAFF PORTAL VIEWS ---
│   │   ├── auth/
│   │   │   └── login.php
│   │   ├── dashboards/
│   │   │   ├── admin_dashboard.php    # Financial overview, all stats
│   │   │   ├── technician_dashboard.php # Personal tasks, pending jobcards
│   │   │   └── accountant_dashboard.php # Unpaid invoices, revenue graphs
│   │   ├── jobcards/
│   │   │   ├── list.php
│   │   │   ├── create.php
│   │   │   ├── edit.php
│   │   │   ├── view.php              # Detailed view incl. costs & timeline
│   │   │   └── approval_queue.php    # Jobcards awaiting manager approval
│   │   ├── costing/
│   │   │   └── manage_costs.php      # Add parts/labor to a specific jobcard
│   │   ├── invoices/
│   │   │   ├── list.php
│   │   │   ├── create.php            # Convert jobcard to invoice
│   │   │   ├── view.php              # Printable invoice view
│   │   │   └── approval_queue.php    # Invoices awaiting payment approval
│   │   ├── financials/
│   │   │   ├── payments.php          # Record/View received payments
│   │   │   └── expenses.php          # Log business expenses (optional)
│   │   ├── reports/
│   │   │   ├── jobcard_reports.php   # Completion rates, avg repair time
│   │   │   ├── financial_reports.php # P&L, Revenue by Tech/Customer
│   │   │   └── inventory_reports.php
│   │   └── settings/
│   │       ├── users.php             # Manage staff accounts
│   │       └── company_profile.php   # Logo, Tax rates, Address
│   │
│   └── portal/                 # --- CLIENT PORTAL VIEWS ---
│       ├── auth/
│       │   ├── login.php             # Client login page
│       │   └── register.php          # Client registration (optional)
│       ├── dashboard.php             # Client overview (open vs closed jobs)
│       ├── jobcard_view.php          # View status, estimated costs
│       ├── approval_prompt.php       # Accept/Reject repair quotes
│       ├── invoices.php              # View historical and current invoices
│       └── make_payment.php         # HTML form for payment gateway integration
│
├── assets/                     # Frontend files
│   ├── css/
│   │   ├── staff.css           # Styling for staff dashboard
│   │   └── portal.css          # Styling for client portal
│   ├── js/
│   │   ├── charts.js           # Chart.js for dashboards
│   │   ├── script.js           # General AJAX (e.g., fetching parts)
│   │   └── pdf_generator.js    # Client-side PDF generation (optional)
│   └── img/
│       └── uploads/            # Jobcard attachments (photos of broken devices)
│
├── database/                   # Database schema and seeders
│   ├── schema.sql              # MySQL table creation script (See below)
│   └── seed_data.sql           # Dummy data for testing
│
├── logs/                       # System logs
│   └── audit_trail.log         # Logs important actions (e.g., "Admin approved invoice #12")
│
├── index.php                   # Main entry point / Router for STAFF
└── portal.php                  # Main entry point / Router for CLIENTS
2. Expanded MySQL Database Schema (database/schema.sql)
To support the new features (approvals, costing, invoicing, client portal), the database needs a robust relational structure.

sql

    approval_type ENUM('quote_review', 'repair_completion') NOT NULL,
    status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
    notes TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (jobcard_id) REFERENCES jobcards(id) ON DELETE CASCADE,
    FOREIGN KEY (approver_id) REFERENCES users(id) ON DELETE SET NULL
);

-- 5. Costing (Parts and Labor)
CREATE TABLE cost_items (
    id INT AUTO_INCREMENT PRIMARY KEY,
    jobcard_id INT NOT NULL,
    description VARCHAR(255) NOT NULL,
    type ENUM('part', 'labor', 'additional_fee') NOT NULL,
    quantity DECIMAL(10,2) DEFAULT 1.00,
    unit_price DECIMAL(10,2) NOT NULL,
    total_price DECIMAL(10,2) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (jobcard_id) REFERENCES jobcards(id) ON DELETE CASCADE
);

-- 6. Invoices
CREATE TABLE invoices (
    id INT AUTO_INCREMENT PRIMARY KEY,
    invoice_number VARCHAR(20) UNIQUE NOT NULL,
    jobcard_id INT NOT NULL,
    customer_id INT NOT NULL,
    subtotal DECIMAL(10,2) NOT NULL,
    tax_rate DECIMAL(5,2) DEFAULT 0.00,
    tax_amount DECIMAL(10,2) DEFAULT 0.00,
    total_amount DECIMAL(10,2) NOT NULL,
    status ENUM('draft', 'sent', 'awaiting_payment', 'paid', 'overdue') DEFAULT 'draft',
    issue_date DATE NOT NULL,
    due_date DATE NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (jobcard_id) REFERENCES jobcards(id) ON DELETE CASCADE,
    FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE
);

-- 7. Payments (Financials)
CREATE TABLE payments (
    id INT AUTO_INCREMENT PRIMARY KEY,
    invoice_id INT NOT NULL,
    amount_paid DECIMAL(10,2) NOT NULL,
    payment_method ENUM('cash', 'credit_card', 'bank_transfer', 'online_gateway') NOT NULL,
    payment_date DATE NOT NULL,
    transaction_ref VARCHAR(100),
    recorded_by INT NOT NULL, -- Links to users.id
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (invoice_id) REFERENCES invoices(id) ON DELETE CASCADE,
    FOREIGN KEY (recorded_by) REFERENCES users(id) ON DELETE RESTRICT
);

-- 8. Audit Trail (Other Relevant Information)
CREATE TABLE audit_logs (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT,
    action VARCHAR(255) NOT NULL,
    entity_type VARCHAR(50), -- e.g., 'jobcard', 'invoice'
    entity_id INT,
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);
3. Workflow Overview based on the Tree
Jobcard Creation & Costing: A technician creates a jobcard (JobcardController). They add parts and labor (CostingController -> cost_items table).
Approval Process: The system generates an Approval Request (ApprovalController). If it's a Quote Approval, the client logs into the Client Portal (portal.php), views the quote, and clicks "Approve" or "Reject".
Invoicing: Once the job is marked "Completed", the Accountant/Admin generates an Invoice (InvoiceController), which calculates tax and totals based on cost_items.
Financials: The client pays via the portal or in-store. The payment is recorded (PaymentController -> payments table). The invoice status changes to "Paid".
Dashboards:
Technician sees their open tasks.
Admin sees overall revenue, pending approvals, and active jobcards.
Accountant sees unpaid invoices and daily revenue.
Reporting: The ReportController queries the database to generate charts showing monthly revenue, average repair times, and technician performance.
use the  Expanded Directory Structure (File Tree) to create system files that matches the workflow 

Thought Process
Here is the foundational code for the expanded system files. I have selected the most critical files from the tree to demonstrate the end-to-end workflow: Creation -> Approval -> Invoicing -> Dashboards.

These files use PDO for database security, separate concerns (MVC pattern), and implement role-based logic.

1. Database Configuration (config/database.php)
The central connection file used by all models and controllers.

php

<?php
// config/database.php
class Database {
    private $host = "localhost";
    private $db_name = "jobcard_db";
    private $username = "root";
    private $password = "";
    public $conn;

    public function getConnection() {
        $this->conn = null;
        try {
            $this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
            $this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        } catch(PDOException $exception) {
            echo "Connection error: " . $exception->getMessage();
        }
        return $this->conn;
    }
}
?>
2. Models (models/Jobcard.php & models/Invoice.php)
These files handle the SQL queries.

php

<?php
// models/Jobcard.php
require_once __DIR__ . '/../config/database.php';

class Jobcard {
    private $conn;
    private $table_name = "jobcards";

    public function __construct($db) {
        $this->conn = $db;
    }

    // Create new jobcard
    public function create($data) {
        $query = "INSERT INTO " . $this->table_name . " 
                  (jobcard_number, customer_id, assigned_to, device_name, serial_number, issue_description, status) 
                  VALUES 
                  (:jobcard_number, :customer_id, :assigned_to, :device_name, :serial_number, :issue_description, 'pending_quote')";
        
        $stmt = $this->conn->prepare($query);
        return $stmt->execute([
            'jobcard_number' => $data['jobcard_number'],
            'customer_id' => $data['customer_id'],
            'assigned_to' => $data['assigned_to'],
            'device_name' => $data['device_name'],
            'serial_number' => $data['serial_number'],
            'issue_description' => $data['issue_description']
        ]);
    }

    // Fetch jobcards by status
    public function getByStatus($status) {
        $query = "SELECT j.*, c.name as customer_name, u.name as tech_name 
                  FROM " . $this->table_name . " j
                  LEFT JOIN customers c ON j.customer_id = c.id
                  LEFT JOIN users u ON j.assigned_to = u.id
                  WHERE j.status = ?";
        $stmt = $this->conn->prepare($query);
        $stmt->execute([$status]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}
?>
3. Staff Router (index.php)
This file routes staff members to the correct views based on the ?page= parameter.

php

<?php
// index.php (Staff Router)
session_start();
require_once 'config/database.php';
require_once 'includes/auth_check.php'; // Ensures staff is logged in

 $page = isset($_GET['page']) ? $_GET['page'] : 'dashboard';
 $action = isset($_GET['action']) ? $_GET['action'] : null;

// Include header
require_once 'includes/staff_header.php'; 

switch($page) {
    case 'dashboard':
        require 'controllers/DashboardController.php';
        break;
    case 'create_jobcard':
        require 'views/staff/jobcards/create.php';
        break;
    case 'approval_queue':
        require 'views/staff/jobcards/approval_queue.php';
        break;
    case 'generate_invoice':
        require 'controllers/InvoiceController.php';
        break;
    default:
        require 'views/staff/dashboards/admin_dashboard.php';
        break;
}

// Include footer
require_once 'includes/staff_footer.php'; 
?>
4. View: Create Jobcard (views/staff/jobcards/create.php)
The HTML form where a technician enters a new job. It posts to JobcardController.php.

php

<!-- views/staff/jobcards/create.php -->
<div class="container mt-4">
    <h2>Create New Jobcard</h2>
    <form action="controllers/JobcardController.php" method="POST">
        <div class="row">
            <div class="col-md-6 mb-3">
                <label>Customer</label>
                <select name="customer_id" class="form-control" required>
                    <?php /* PHP loop to fetch customers goes here */ ?>
                    <option value="1">John Doe</option>
                </select>
            </div>
            <div class="col-md-6 mb-3">
                <label>Assign To (Technician)</label>
                <select name="assigned_to" class="form-control">
                    <option value="2">Tech Mike</option>
                </select>
            </div>
        </div>
        <div class="row">
            <div class="col-md-6 mb-3">
                <label>Device Name</label>
                <input type="text" name="device_name" class="form-control" required>
            </div>
            <div class="col-md-6 mb-3">
                <label>Serial Number</label>
                <input type="text" name="serial_number" class="form-control">
            </div>
        </div>
        <div class="mb-3">
            <label>Issue Description</label>
            <textarea name="issue_description" class="form-control" rows="4" required></textarea>
        </div>
        <button type="submit" name="create_jobcard" class="btn btn-primary">Create Jobcard & Request Quote</button>
    </form>
</div>
5. Controller: Jobcard Creation (controllers/JobcardController.php)
Receives the form data, generates a jobcard number, saves it, and triggers the approval process.

php

<?php
// controllers/JobcardController.php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../models/Jobcard.php';
require_once __DIR__ . '/../models/Approval.php';

if (isset($_POST['create_jobcard'])) {
    $database = new Database();
    $db = $database->getConnection();

    $jobcard = new Jobcard($db);
    $approval = new Approval($db);

    // Generate Jobcard Number
    $data = [
        'jobcard_number' => 'JC-' . date("Ymd") . '-' . rand(100, 999),
        'customer_id' => $_POST['customer_id'],
        'assigned_to' => $_POST['assigned_to'],
        'device_name' => $_POST['device_name'],
        'serial_number' => $_POST['serial_number'],
        'issue_description' => $_POST['issue_description']
    ];

    if ($jobcard->create($data)) {
        $jobcard_id = $db->lastInsertId();
        
        // Create an Approval Request for the Quote
        $approval->createRequest($jobcard_id, 'quote_review', $_SESSION['user_id']);
        
        header("Location: ../index.php?page=dashboard&status=jobcard_created");
    } else {
        echo "Error creating jobcard.";
    }
}
?>
6. Client Portal: Approval Prompt (views/portal/approval_prompt.php)
This is what the client sees when they log in. They can approve or reject the quote, which moves the workflow forward.

php

<!-- views/portal/approval_prompt.php -->
<?php
// Assume $jobcard_data is passed from ClientPortalController containing job details and costs
?>
<div class="container mt-5">
    <div class="card">
        <div class="card-header bg-warning">
            <h4 class="mb-0">Action Required: Quote Approval for Job #<?php echo htmlspecialchars($jobcard_data['jobcard_number']); ?></h4>
        </div>
        <div class="card-body">
            <h5>Device: <?php echo htmlspecialchars($jobcard_data['device_name']); ?></h5>
            <p><strong>Issue:</strong> <?php echo htmlspecialchars($jobcard_data['issue_description']); ?></p>
            
            <hr>
            <h5>Estimated Costs:</h5>
            <table class="table table-bordered">
                <thead>
                    <tr><th>Description</th><th>Type</th><th class="text-end">Price</th></tr>
                </thead>
                <tbody>
                    <!-- PHP loop to display cost_items goes here -->
                    <tr><td>Labor (2 hours)</td><td>Labor</td><td class="text-end">$50.00</td></tr>
                    <tr><td>Replacement Screen</td><td>Part</td><td class="text-end">$120.00</td></tr>
                </tbody>
                <tfoot>
                    <tr><th colspan="2" class="text-end">Total:</th><th class="text-end">$170.00</th></tr>
                </tfoot>
            </table>

            <form action="../../controllers/ApprovalController.php" method="POST">
                <input type="hidden" name="jobcard_id" value="<?php echo $jobcard_data['id']; ?>">
                <div class="mb-3">
                    <label>Notes (Optional)</label>
                    <textarea name="notes" class="form-control" rows="2"></textarea>
                </div>
                <button type="submit" name="action" value="approve_quote" class="btn btn-success">Approve & Proceed with Repair</button>
                <button type="submit" name="action" value="reject_quote" class="btn btn-danger">Reject Quote</button>
            </form>
        </div>
    </div>
</div>
7. Controller: Approval Processing (controllers/ApprovalController.php)
Handles the logic when a client approves/rejects a quote or a staff member approves an invoice.

php

<?php
// controllers/ApprovalController.php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../models/Approval.php';
require_once __DIR__ . '/../models/Jobcard.php';

if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['action'])) {
    $database = new Database();
    $db = $database->getConnection();
    
    $approval = new Approval($db);
    $jobcard = new Jobcard($db);
    
    $jobcard_id = $_POST['jobcard_id'];
    $action = $_POST['action'];
    $notes = $_POST['notes'] ?? '';

    if ($action == 'approve_quote') {
        // Client approved the quote. Update jobcard status to in_progress
        $jobcard->updateStatus($jobcard_id, 'in_progress');
        $approval->updateStatus($jobcard_id, 'quote_review', 'approved', $notes);
        
        header("Location: ../portal.php?page=dashboard&status=approved");
        
    } elseif ($action == 'reject_quote') {
        // Client rejected. Cancel or hold jobcard
        $jobcard->updateStatus($jobcard_id, 'cancelled');
        $approval->updateStatus($jobcard_id, 'quote_review', 'rejected', $notes);
        
        header("Location: ../portal.php?page=dashboard&status=rejected");
    }
}
?>
8. Controller: Invoice Generation (controllers/InvoiceController.php)
Triggered when a job is marked "completed". It calculates totals from cost_items and generates an invoice.

php

<?php
// controllers/InvoiceController.php
require_once __DIR__ . '/../config/database.php';

if (isset($_GET['jobcard_id'])) {
    $database = new Database();
    $db = $database->getConnection();
    $jobcard_id = $_GET['jobcard_id'];

    try {
        $db->beginTransaction();

        // 1. Fetch all costs for this jobcard
        $stmt = $db->prepare("SELECT SUM(total_price) as subtotal FROM cost_items WHERE jobcard_id = ?");
        $stmt->execute([$jobcard_id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        $subtotal = $row['subtotal'] ?? 0;
        
        // 2. Calculate Tax (e.g., 10% VAT)
        $tax_rate = 10.00;
        $tax_amount = ($subtotal * $tax_rate) / 100;
        $total_amount = $subtotal + $tax_amount;
        
        // 3. Generate Invoice Number
        $invoice_number = 'INV-' . date("Ymd") . '-' . rand(100, 999);
        $issue_date = date('Y-m-d');
        $due_date = date('Y-m-d', strtotime('+14 days')); // Due in 14 days
        
        // 4. Fetch customer_id from jobcard
        $stmt = $db->prepare("SELECT customer_id FROM jobcards WHERE id = ?");
        $stmt->execute([$jobcard_id]);
        $jobcard = $stmt->fetch(PDO::FETCH_ASSOC);
        
        // 5. Insert into invoices table
        $stmt = $db->prepare("INSERT INTO invoices (invoice_number, jobcard_id, customer_id, subtotal, tax_rate, tax_amount, total_amount, status, issue_date, due_date) VALUES (?, ?, ?, ?, ?, ?, ?, 'sent', ?, ?)");
        $stmt->execute([$invoice_number, $jobcard_id, $jobcard['customer_id'], $subtotal, $tax_rate, $tax_amount, $total_amount, $issue_date, $due_date]);
        
        // 6. Update Jobcard status to 'completed' (or 'awaiting_payment')
        $db->prepare("UPDATE jobcards SET status = 'completed' WHERE id = ?")->execute([$jobcard_id]);

        $db->commit();
        
        // Redirect to invoice view
        header("Location: ../index.php?page=invoices&status=invoice_generated");

    } catch(Exception $e) {
        $db->rollBack();
        echo "Failed to generate invoice: " . $e->getMessage();
    }
}
?>
9. View: Admin Dashboard (views/staff/dashboards/admin_dashboard.php)
Displays high-level financials and operational metrics.

php

<!-- views/staff/dashboards/admin_dashboard.php -->
<?php
// Assume $stats array is passed from DashboardController containing DB counts
?>
<div class="container-fluid mt-4">
    <h2>Admin Dashboard</h2>
    <div class="row">
        <!-- Pending Approvals Card -->
        <div class="col-md-3 mb-4">
            <div class="card border-left-warning shadow h-100 py-2">
                <div class="card-body">
                    <div class="text-xs font-weight-bold text-warning text-uppercase mb-1">Awaiting Client Approval</div>
                    <div class="h5 mb-0 font-weight-bold text-gray-800"><?php echo $stats['pending_approvals']; ?> Jobcards</div>
                </div>
            </div>
        </div>
        
        <!-- In Progress Card -->
        <div class="col-md-3 mb-4">
            <div class="card border-left-primary shadow h-100 py-2">
                <div class="card-body">
                    <div class="text-xs font-weight-bold text-primary text-uppercase mb-1">Repairs In Progress</div>
                    <div class="h5 mb-0 font-weight-bold text-gray-800"><?php echo $stats['in_progress']; ?> Jobcards</div>
                </div>
            </div>
        </div>
        
        <!-- Unpaid Invoices Card -->
        <div class="col-md-3 mb-4">
            <div class="card border-left-danger shadow h-100 py-2">
                <div class="card-body">
                    <div class="text-xs font-weight-bold text-danger text-uppercase mb-1">Outstanding Invoices</div>
                    <div class="h5 mb-0 font-weight-bold text-gray-800"><?php echo $stats['unpaid_invoices']; ?> Invoices</div>
                </div>
            </div>
        </div>

        <!-- Monthly Revenue Card -->
        <div class="col-md-3 mb-4">
            <div class="card border-left-success shadow h-100 py-2">
                <div class="card-body">
                    <div class="text-xs font-weight-bold text-success text-uppercase mb-1">Revenue (This Month)</div>
                    <div class="h5 mb-0 font-weight-bold text-gray-800">$<?php echo number_format($stats['monthly_revenue'], 2); ?></div>
                </div>
            </div>
        </div>
    </div>

    <!-- Chart placeholder for reporting -->
    <div class="card mt-4 shadow">
        <div class="card-header">
            <h6 class="m-0 font-weight-bold text-primary">Financial & Jobcard Trends</h6>
        </div>
        <div class="card-body">
            <canvas id="revenueChart" height="80"></canvas>
            <!-- Chart.js logic would go in staff_footer.php -->
        </div>
    </div>
</div>


